#pragma once
#ifndef _FEM_DELAUNAY_TRIANGULATION_
#define _FEM_DELAUNAY_TRIANGULATION_

//================================================================================================================================
//================================================================================================================================
#include <QtCore/QHash>
#include <QtCore/QList>
#include <QtCore/QVector>
#include <QtCore/QStringList>

#include "core_global.h"
#include "geometry/2d/point.h"
#include "fem/bordersegment.h"
#include "fem/trianglenet.h"
#include "fem/triangleelement.h"
//================================================================================================================================
//================================================================================================================================
namespace fem
{
    /// Класс, реализующий триангуляцию Делоне расчетной области.
    class CORE_EXPORT DelaunayTriangulation
    {
    public:
        /// \param f_ids Текстовые идентификаторы функций, значения которых ищутся на данной сетке.
        explicit DelaunayTriangulation(const QStringList &f_ids);

        ~DelaunayTriangulation()
        {
            for(int i = 0; i < 60; ++i)
                delete [] m_Cache[i];
            delete [] m_Cache;
        }

        /// Добавть точку в триангуляцию.
        /// \return Индекс добавленной точки.
        int addPoint(const geometry::_2d::Point &point)
        {
            m_Points.append(point);
            return m_Points.size() - 1;
        }

        /// Добавить точку в триангуляцию и привязать ее к суперструктуре.
        /// \return Индекс добавленной точки.
        int addPoint(int superstructure, const geometry::_2d::Point &point)
        {
            m_Points.append(point);
            m_Superstructures[superstructure].Points.append(m_Points.size() - 1);
            return m_Points.size() - 1;
        }

        /// Добавить точку триангуляции в суперструктуру.
        /// \return Индекс добавленной точки.
        int addPoint(int superstructure, int point)
        {
            m_Superstructures[superstructure].Points.append(point);
            return point;
        }

        /// Добавление сегмента границы расчетной области.
        /// \return Индекс добавленного сегмента.
        int addBorderSegment(const BorderSegment &segment)
        {
            m_BorderSegments.append(segment);
            return m_BorderSegments.size() - 1;
        }

        /// Создание сегмента границы расчетной области.
        /// \param begin Индекс опорной точки, являющейся началом сегмента.
        /// \param end Индекс опорной точки, являющейся концом сегмента.
        /// \param num_of_knots Число точек расчетной области, которые будут расположены вдоль сегмента.
        /// \return Индекс добавленного сегмента.
        /// \remarks \e begin и \e end - индексы существующих опорных точек.
        int addBorderSegment(int begin, int end, int num_of_knots);

        /// Добавление всех точек сегмента в суперструктуру
        void addBorderSegments(int superstructure, const QList<int> &segments);

        /// Добавить суперструктуру.
        /// \return Индекс добавленной суперструктуры.
        int addSuperstructure(const QList<int> points, int material)
        {
            Q_ASSERT_X(points.size() == 3 || points.size() == 4, "DelaunayTriangulation::addSuperstructure", "unsupported superstructure shape");

            Superstructure superstructure;
            superstructure.Points = points;
            superstructure.Material = material;
            superstructure.NumOfVertices = points.size();

            m_Superstructures.append(superstructure);
            return m_Superstructures.size() - 1;
        }

        //! Создание конечноэлементной сетки.
        TriangleNet triangulate();

        /// Добавление дополнительной информации о краевых условиях на сегменте границы расчетной области.
        //
        /// Для типа краевого условия допустимы следующие значения:\n
        /// 1 - Краевое условие Дирихле.\n
        /// 2 - Краевое условие Неймана.\n
        /// 3 - Краевое условие Коши.\n
        /// 4 - Условие идеального контакта.\n
        /// Для условий идеального контакта параметр \e bc_formula игнорируется. При создании границ с вышеназванными краевыми
        /// условиями для параметра \e bc_formula можно оставить значение по умолчанию. При использовании значения по умолчания
        /// для КУ другого типа будет выводиться предупреждение.
        /// \param f_id Индекс функции, для которой задается информация о КУ.
        /// \param segment      Индекс сегмента границы расчетной области, для которого добавляется информация.
        /// \param bc_type      Тип краевого условия ассоциированного с сегментом.
        /// \param bc_formula   Номер формулы по которой расчитывается значение краевого условия.
        /// \sa addBasisPoint().
        void addBorderSegmentBCInfo(const QString &f_id, int segment, int bc_type, int bc_formula = -1);

        /// Получение информации об участках границы расчетной области с заданными на них первыми краевыми условиями.
        /// \param f_id Идентификатор функции, для которой нужно получить информацию о краевых условиях.
        /// \param[out] points Номера узлов, в которых задано краевой условие первого рода.
        /// \param[out] conditions Номера формул по которым вычисляются значения краевого условия в точках.
        void getDirichletBoundaryInfo(const QString &f_id, QList<int> &points, QList<int> &conditions) const;

    private:
        //************************************************************************************************************************
        //************************************************************************************************************************
        struct Rib;
        struct Triangle;

        //************************************************************************************************************************
        //************************************************************************************************************************
        /// Добавление треугольника в кэш для последующего быстрого поиска.
        void addToCache(Triangle *triangle);

        /// Поиск треугольника, которому принадлежит указанная точка.
        /// \param point Индекс точки, для которой нужно найти содержащий её треугольник.
        /// \param[out] divided_rib Указатель на ребро треугольника, на котором находится точка \e point. NULL - если точка лежит не на ребре.
        /// \return Указатель на треугольник, которому принадлежит точка \e point.
        Triangle* findTriangle(int point, Rib **divided_rib);

        /// Проверка треугольника на соответствие условию Делоне.
        void testTriangle(Triangle* triangle);

        /// Проверка треугольника на соответствие условию Делоне относительно соседа по ребру \e conj_rib.
        bool testDelaunay(Rib* conj_rib);

        /// Триангуляция треугольной суперструктуры.
        void prepareTriangleSuperstructure(int superstructure);
        /// Триангуляция четырехугольной суперструктуры.
        void prepareQuadrangleSuperstructure(int superstructure);

        /// Добавление двух смежных ребер [point1; point2] и [point2; point1].
        /// \param point1 Первый конец ребра.
        /// \param point2 Второй конец ребра.
        /// \param[out] rib1 Ребро [point1; point2].
        /// \param[out] rib2 Ребро [point2; point1].
        /// \remarks Указатели \e rib1 и \e rib2 должны адресовать существующие обхекты.
        void addRib(int point1, int point2, Rib *rib1, Rib *rib2)
        {
            rib1->Begin = point1;
            rib1->End = point2;
            rib1->Twin = rib2;

            rib2->Begin = point2;
            rib2->End = point1;
            rib2->Twin = rib1;
        }

        //************************************************************************************************************************
        //************************************************************************************************************************
        /// Информация о суперструктуре.
        struct Superstructure
        {
            /// \brief Количество вершин у суперструктуре.
            ///
            /// Вершинами являются первые \e numOfVertices точек, принадлежащих суперструктуре.
            int NumOfVertices;

            /// Материал, соответствующий суперструктуре.
            int Material;

            /// Список индексов точек, принадлежащих суперструктуре.
            QList<int> Points;

            /// Список треугольников, находящихся внутри суперструктуры.
            QList<Triangle*> Triangles;

            /// Список ребер треугольников.
            QList<Rib*> Ribs;

            /// Изменение существующего треугольника суперструктуры.
            /// \param[in,out] rib1 Первое ребро треугольника.
            /// \param[in,out] rib2 Второе ребро треугольника.
            /// \param[in,out] rib3 Третье ребро треугольника.
            /// \return Ссылка на добавленный треугольник.
            /// \remarks Функция связывает ребра так, чтобы i-е ребро указывало на i+1-е ребро.
            void setTriangle(Triangle* triangle, Rib *rib1, Rib *rib2, Rib *rib3)
            {
                rib1->Next = rib2;
                rib2->Next = rib3;
                rib3->Next = rib1;

                triangle->Ribs[0] = rib1;
                triangle->Ribs[1] = rib2;
                triangle->Ribs[2] = rib3;
            }

            Rib* addRibs(int n)
            {
                Rib *ribs = new Rib[n];
                for(int i = 0; i < n; ++i)
                    Ribs.append(ribs + i);
                return ribs;
            }

            /// Добавление треугольника в суперструктуру.
            /// \param[in,out] rib1 Первое ребро треугольника.
            /// \param[in,out] rib2 Второе ребро треугольника.
            /// \param[in,out] rib3 Третье ребро треугольника.
            /// \return Ссылка на добавленный треугольник.
            /// \remarks Функция связывает ребра так, чтобы i-е ребро указывало на i+1-е ребро.
            Triangle* addTriangle(Rib *rib1, Rib *rib2, Rib *rib3)
            {
                rib1->Next = rib2;
                rib2->Next = rib3;
                rib3->Next = rib1;

                Triangle *triangle = new Triangle();
                triangle->Ribs[0] = rib1;
                triangle->Ribs[1] = rib2;
                triangle->Ribs[2] = rib3;
                Triangles.append(triangle);

                return Triangles.last();
            }
        };
        /// Список суперструктур.
        QList<Superstructure> m_Superstructures;

        //************************************************************************************************************************
        //************************************************************************************************************************
        /// Информация о ребрах триангуляции.
        struct Rib
        {
            int Begin;              ///< Начало ребра.
            int End;                ///< Конец ребра.
            Rib* Next;              ///< Следующее (по часовой стрелке) ребро.
            Rib* Twin;              ///< Смежное ребро.
            Triangle* Neighbour;    ///< Соседний треугольник.
        };

        //************************************************************************************************************************
        //************************************************************************************************************************
        /// Информация о треугольниках.
        struct Triangle
        {
            Rib* Ribs[3];           ///< Ребра треугольника.
        };

        //************************************************************************************************************************
        //************************************************************************************************************************
        /// Кэш для быстрого поиска треугольников.
        QList<Triangle*> **m_Cache;
        /// Размер ячейки кэша
        double m_CacheStep;

        int m_CurStruct;

        //************************************************************************************************************************
        //************************************************************************************************************************
        /// Информация о краевых условиях для сегмента границы расчетной области.
        struct BorderSegmentBCInfo
        {
            /// Индекс сегмента границы расчетной области.
            int Segment;

            /// \brief Тип краевого условия.
            ///
            /// Для типа краевого условия допустимы следующие значения:\n
            /// 1 - Краевое условие Дирихле.\n
            /// 2 - Краевое условие Неймана.\n
            /// 3 - Краевое условие Коши.\n
            /// 4 - Условие идеального контакта.
            int BCType;

            /// \brief Номер формулы, по которой вычисляется значение КУ.
            ///
            /// Для условий идеального контакта (BCType = 4) параметр BCFormula игнорируется.
            int BCFormula;
        };
        /// Список сегментов границы с заданными на них КУ для каждой функции.
        QVector< QList<BorderSegmentBCInfo> > m_BorderSegmentsBCInfo;
        /// Таблица сопоставления текстового идентификатора функции числовому.
        QHash<QString, int> m_FunctionsIDTable;

        //************************************************************************************************************************
        //************************************************************************************************************************
        QList<geometry::_2d::Point> m_Points;       ///< Набор точек расчетной области, служащих выршинами конечных элементов.
        QList<BorderSegment> m_BorderSegments;      ///< Набор сегментов, разбивающих расчетную область на подобласти.
        QList<TriangleElement> m_TriangleElements;  ///< Набор треугольных конечных элементов.
    };
}

#endif // _FEM_DELAUNAY_TRIANGULATION_
